#include "atTriangle.h"
#include "atUtil.h"
#include <trax.hpp>

namespace AtomTrace{
    atTriangle::atTriangle():
    		matID(-1)
    {
        // default constructor of Vector3 should initialize all 3 vertices as zero vector, so no need to explicit init here.
    }

    atTriangle::atTriangle(const Vector3 & iv0, const Vector3 & iv1, const Vector3 & iv2):
    	matID(0),
        v0(iv0),
        e1(iv1 - iv0),
        e2(iv2 - iv0)
    {
        //N = e1.Cross(e2);
        //N.Normalize();
    }

    atTriangle::atTriangle(int i_addr)
    {
        LoadFromGM(i_addr);
    }

    const atTriangle & atTriangle::LoadFromGM(int i_addr)
    {
        //int address = i_addr;
        v0.LoadFromGM(i_addr);
        Vector3 tv1(i_addr+3);
        Vector3 tv2(i_addr+6);
        matID = loadi(i_addr, 10);
        e1 = tv1 - v0;
        e2 = tv2 - v0;

        //N = e1.Cross(e2); // I should make sure which hand coordinate system I am working on.
        //N.Normalize();

        return *this;
    }

    const Vector3 & atTriangle::operator[](int index) const
    {
        if(index > 2){
            //error or warning should assert here
        }

        return *((&v0)+index);
    }

    Vector3 & atTriangle::operator[](int index)
    {
        if(index > 2){
            //error or warning should assert here
        }

        return *((&v0)+index);
    }

    bool atTriangle::Intersect(const Ray & ray, HitRecord & hRec)
    {
        //return _IntersectAreaMethod(ray, hRec);
        return _IntersectDenoMethod(ray, hRec);
    }

    // **** Private functions ****
    bool atTriangle::_IntersectAreaMethod(const Ray & ray, HitRecord & hRec)
    {
        const float eps = 0.0000001f; // epsilon
        // calculate surface normal
        //Vector3 v01 = v1 - v0;
        //Vector3 v02 = v2 - v0;

        //N = e1.Cross(e2);
        //N.Normalize();

        Vector3 N = GetCrossNormal(e1, e2);

        float cosTnd = N.Dot(ray._dir);
        if(cosTnd > 1.0f) cosTnd = 1.0f;
        // first check parallel
        if(cosTnd < eps && cosTnd > -eps) // parallel
            return false;

        // ray plane intersection
        float t = (v0 - ray._o).Dot(N) / cosTnd;

        if(t > hRec.t || t < 0.0f) return false;
        //if(t < 0.0f) return false; // ray doesn't care interstection that happen behind its origin

        Vector3 hitP = ray._o + ray._dir * t;

        // barycentric coordinate detection
        Vector3 v0h = hitP - v0;

        float a0 = ( e1.Cross(e2) ).Dot(N);
        float a1 = ( e1.Cross(v0h)).Dot(N);
        float a2 = (v0h.Cross(e2) ).Dot(N);

        a0 = 1.0f / a0;
        a1 *= a0;
        a2 *= a0;

        if(a1 < eps || a2 < eps || a1 + a2 > 1.0f + eps)
            return false;

        hRec.P = hitP;
        hRec.N = N; // so far no vertex normal yet for lab3
        hRec.t = t;
        hRec.matID = matID;

        //hRec.e1 = e1;
        //hRec.e2 = e2;

        return true;
    }

    bool atTriangle::_IntersectDenoMethod(const Ray & ray, HitRecord & hRec)
    {
        const float eps = 0.0000001f; // epsilon
        // V is ray._dir, O is ray._o
        Vector3 r1;
        r1 = ray._dir.Cross(e2);
        float denom = e1.Dot(r1);

        if(absf(denom) < eps) return false; // miss

        float invDenom = 1.0f / denom;
        Vector3 s = ray._o - v0;
        float b1 = s.Dot(r1) * invDenom;

        if(b1 < 0 || b1 > 1) return false; // miss

        Vector3 r2 = s.Cross(e1);
        float b2 = ray._dir.Dot(r2) * invDenom;

        if(b2 < 0 || b1 + b2 > 1) return false; //miss

        float t = e2.Dot(r2) * invDenom; // hit!

        if(t < 0.0f || t > hRec.t) return false; // hit but farther than hRec.t

        hRec.t = t;
        hRec.P = ray._o + ray._dir * t;
        hRec.matID = matID;
        hRec.e1 = e1;
        hRec.e2 = e2;

        hRec.incDir = ray._dir;
        hRec.incDir.Normalize();

        return true;
    }

}
